package com.inetools.gwetons.client;

import java.util.ArrayList;
import java.util.LinkedHashSet;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.inetools.gwetons.shared.timer.TimerControllerFactoryUniInstanceHolder;
import com.inetools.gwetons.shared.timer.TimerControllerIntf;
import com.inetools.gwetons.shared.timer.TimerEventListenerIntf;

//TODO create interface, and use that interface to change the style

public class SimpleStyleProgressiveChanger {
	// Defining data structures by target object type ---------------------------------------------
	public interface StyleAccessor {
		public void setStyle( String style, String value);
		public String getStyle();
		public int getInitValue();
		public int getEndValue();
		public boolean isFlaggedAsValueForColor();
	}
	public interface ElementStyleAccessor extends StyleAccessor {
		public Element getElement();
	}
	public interface DomIdStyleAccessor extends ElementStyleAccessor {
		public String getId();
	}
	public interface CssRuleStyleAccessor extends StyleAccessor {
		public JsCssRule getCssRule();
	}
	
	public class StyleChangerBaseUnit {
		public String style = null;
		public String getStyle() {
			return style;
		}
		protected int initValue = 0;
		public int getInitValue() {
			return initValue;
		}
		protected int endValue = 0;
		public int getEndValue() {
			return endValue;
		}
		protected boolean flaggedAsValueForColor = false;
		public boolean isFlaggedAsValueForColor() {
			return flaggedAsValueForColor;
		}
		
		public StyleChangerBaseUnit( 
				String style, int initValue, int endValue, boolean flaggedAsValueForColor) {
			this.style = style;
			this.initValue = initValue;
			this.endValue = endValue;
			this.flaggedAsValueForColor = flaggedAsValueForColor;
		}
	} // public class StyleChangerBaseUnit
	
	public class ElementStyleChangerUnit extends StyleChangerBaseUnit implements ElementStyleAccessor {
		protected Element element;
		@Override
		public Element getElement() {
			return element;
		}
		public ElementStyleChangerUnit( Element element, String style, int initValue, int endValue, boolean flaggedAsValueForColor) {
			super( style, initValue, endValue, flaggedAsValueForColor);
			this.element = element;
		}
		
		@Override
		public void setStyle(String style, String value) {
			if ( ( style == null) || "".equals( style)) return;
			if ( ( value == null) || "".equals( value)) return;
			
			element.getStyle().setProperty( style, value);
		}
	} // public class ElementStyleChangerUnit extends StyleChangerBaseUnit implements ElementStyleAccessor
	
	public class DomIdStyleChangerUnit extends ElementStyleChangerUnit implements DomIdStyleAccessor {
		protected String idStr;
		@Override
		public String getId() {
			if ( "".equals( idStr)) return null;
			return idStr;
		}
		public DomIdStyleChangerUnit( String idStr, String style, int initValue, int endValue, boolean flaggedAsValueForColor) {
			super( null, style, initValue, endValue, flaggedAsValueForColor);
			this.idStr = idStr;
			if ( ( idStr != null) && !"".equals( idStr)) {
				super.element = Document.get().getElementById( idStr);
			}
		}
	} // public class DomIdStyleChangerUnit extends ElementStyleChangerUnit implements DomIdStyleAccessor
	
	public class CssRuleStyleChangerUnit extends StyleChangerBaseUnit implements CssRuleStyleAccessor {
		protected JsCssRule jsCssRule;
		@Override
		public JsCssRule getCssRule() {
			return jsCssRule;
		}
		public CssRuleStyleChangerUnit( 
				JsCssRule jsCssRule, String style, int initValue, int endValue, boolean flaggedAsValueForColor) {
			super( style, initValue, endValue, flaggedAsValueForColor);
			this.jsCssRule = jsCssRule;
		}
		@Override
		public void setStyle( String style, String value) {
			jsCssRule.setStyle( style, value);
		}
	} // public class CssRuleStyleChangerUnit extends StyleChangerBaseUnit implements CssRuleStyleAccessor
	
	// --------------------------------------------------------------------------------------------
	
	// time related fields ------------------------------------------------------------------------
	private int period = 2400;
	public int getPeriod() {
		return period;
	}
	public boolean setPeriod(int period) {
		if ( period < interval) return false; 
		this.period = period;
		return true;
	}
	
	private int interval = 240;
	public int getInterval() {
		return interval;
	}
	public boolean setInterval(int interval) {
		if ( period < interval) return false;
		this.interval = interval;
		return true;
	}
	// --------------------------------------------------------------------------------------------

	// behavior related fields --------------------------------------------------------------------
	protected boolean repeat;
	protected boolean reversable;
	// --------------------------------------------------------------------------------------------
	
	// timer controller ---------------------------------------------------------------------------
	private TimerControllerIntf timerController 
	= TimerControllerFactoryUniInstanceHolder.getTimerController();
		public TimerControllerIntf getTimerController() {
			return timerController;
		}
		public void setTimerController( TimerControllerIntf timerController) {
			this.timerController = timerController;
		}
	// --------------------------------------------------------------------------------------------
	
	// Constructor
	public SimpleStyleProgressiveChanger( boolean reversable, boolean repeat) {
		this.repeat = repeat;
		this.reversable = reversable;
	} // public SimpleStyleProgressiveChanger( boolean reversable, boolean repeat)

	protected class SimpleStyleProgressiveChangerTimerEvent implements TimerEventListenerIntf {
		protected int interval, period;
		protected boolean repeat;
		
		protected boolean reversable = true;
		public boolean isReversable() {
			return reversable;
		}
		public void setReversable(boolean reversable) {
			this.reversable = reversable;
		}
		protected boolean reversingFlag = false;
		
		protected boolean backword = false;
		
		protected String getRGBColorHexStr( int color) {
			String colorStr = Integer.toHexString( color);
			if ( colorStr.length() > 6) {
				colorStr = colorStr.substring( 0, 6);
			}
			else {
				String complementDigitStr = "0";
				if ( color < 0) complementDigitStr = "F";
				while( colorStr.length() < 6) {
					colorStr = complementDigitStr + colorStr;
				} // while
			}
			return colorStr;
		}
		
		protected int[] getRGBColorElements( int color) {
			String colorStr = getRGBColorHexStr( color);
			
			int blue = Integer.parseInt( colorStr.substring( colorStr.length() - 2), 16);
			int green = Integer.parseInt( colorStr.substring( colorStr.length() - 4, colorStr.length() - 2), 16);
			int red = Integer.parseInt( colorStr.substring( colorStr.length() - 6, colorStr.length() - 4), 16);
			return new int[]{ red, green, blue};
		}

		protected LinkedHashSet<StyleAccessor> styleAccessorLinkedHashSet;
		
		protected class StyleChangerState {
			public StyleAccessor styleAccessor;
			
			public boolean forProcessFlag = false;
			
			public int currentValue;
			public int diffUnit;
			
			public int initBlue, initGreen, initRed;
			public int currentBlue, currentGreen, currentRed;
			public int endBlue, endGreen, endRed;
			public int diffBlueUnit, diffGreenUnit, diffRedUnit;
		} // protected class StyleChangerState
		
		protected ArrayList<StyleChangerState> styleChangerStateArrayList = 
			new ArrayList<StyleChangerState>();
		
		protected ChangeEventType changeEventType = ChangeEventType.Sequence;
		
		// Constructor 
		/**
		 * @param changingStyleLinkedHashSet: element property of this has the value set
		 * @param interval
		 * @param period
		 * @param reversable
		 * @param repeat
		 */
		public SimpleStyleProgressiveChangerTimerEvent( 
				LinkedHashSet<StyleAccessor> styleAccessorLinkedHashSet, 
				ChangeEventType changeEventType, 
				int interval, int period, boolean reversable, boolean repeat) 
		{
			if ( styleAccessorLinkedHashSet == null) return;
			this.styleAccessorLinkedHashSet = styleAccessorLinkedHashSet;

			this.interval = interval;
			this.period = period;
			if ( interval > period) return;
			
			if ( changeEventType != null) {
				this.changeEventType = changeEventType;
			}
			
			int styleChangerBaseUnitIndex = 0;
			for( StyleAccessor styleAccessor : styleAccessorLinkedHashSet) {
				StyleChangerState styleChangerState = new StyleChangerState();
				styleChangerState.styleAccessor = styleAccessor;
				
				// Mark styleChangerState for processing ------------------------------------------
				switch( changeEventType) {
					case OnceAtAll:
						// rise forProcessFlag up for all entries
						styleChangerState.forProcessFlag = true;
						break;
					case RoundTrip:
					case Sequence:
					default:
						// rise forProcessFlag up only for the first entry
						if ( ++styleChangerBaseUnitIndex == 1) {
							styleChangerState.forProcessFlag = true;
						}
						break;
				} // switch
				// --------------------------------------------------------------------------------

				// Setup about initial value ------------------------------------------------------
				int styleInitValue = styleAccessor.getInitValue();
				styleChangerState.currentValue = styleInitValue;
				
				int[] rgbColorIntArray;
				if ( styleAccessor.isFlaggedAsValueForColor()) {
					String colorStr = getRGBColorHexStr( styleInitValue);
					styleAccessor.setStyle( styleAccessor.getStyle(), "#" + colorStr);
					
					rgbColorIntArray = getRGBColorElements( styleInitValue);
					styleChangerState.initBlue = rgbColorIntArray[ 2];
					styleChangerState.initGreen = rgbColorIntArray[ 1];
					styleChangerState.initRed = rgbColorIntArray[ 0];
					styleChangerState.currentBlue = rgbColorIntArray[ 2];
					styleChangerState.currentGreen = rgbColorIntArray[ 1];
					styleChangerState.currentRed = rgbColorIntArray[ 0];
				}
				else { // Style is not about color
					String initValueStr = String.valueOf( styleInitValue);
					styleAccessor.setStyle( styleAccessor.getStyle(), initValueStr);
					if ( "opacity".equalsIgnoreCase( styleAccessor.getStyle())) {
						styleAccessor.setStyle(  
								"filter", 
								":alpha(opacity=" + String.valueOf( styleInitValue * 100) + ")");
					}
				}
				// --------------------------------------------------------------------------------
				
				// Setup about unit of difference between initial value and end value -------------
				int intervalCount = 0;
				if ( interval <= 0) {
					intervalCount = 1;
				}
				else {
					intervalCount = period/interval;
				}
//TODO add statements for the case: period <=0		
				
				int styleEndValue = styleAccessor.getEndValue();
				styleChangerState.diffUnit = 
					( styleEndValue - styleInitValue) /intervalCount;
				
				if ( styleAccessor.isFlaggedAsValueForColor()) {
					rgbColorIntArray = getRGBColorElements( styleEndValue);
					
					styleChangerState.diffBlueUnit = 
						( rgbColorIntArray[ 2] - styleChangerState.currentBlue) /intervalCount;
					styleChangerState.diffGreenUnit = 
						( rgbColorIntArray[ 1] - styleChangerState.currentGreen) /intervalCount;
					styleChangerState.diffRedUnit = 
						( rgbColorIntArray[ 0] - styleChangerState.currentRed) /intervalCount;
				}
/*
GWT.log( "styleChangerState.initBlue = " + getRGBColorHexStr( styleChangerState.initBlue), null);
GWT.log( "styleChangerState.currentBlue = " + getRGBColorHexStr( styleChangerState.currentBlue), null);
GWT.log( "styleChangerState.diffBlueUnit = " + styleChangerState.diffBlueUnit, null);
GWT.log( "styleChangerState.initGreen = " + getRGBColorHexStr( styleChangerState.initGreen), null);
GWT.log( "styleChangerState.currentGreen = " + getRGBColorHexStr( styleChangerState.currentGreen), null);
GWT.log( "styleChangerState.diffGreenUnit = " + styleChangerState.diffGreenUnit, null);
GWT.log( "styleChangerState.initRed = " + getRGBColorHexStr( styleChangerState.initRed), null);
GWT.log( "styleChangerState.currentRed = " + getRGBColorHexStr( styleChangerState.currentRed), null);
GWT.log( "styleChangerState.diffRedUnit = " + styleChangerState.diffRedUnit, null);
*/
				// --------------------------------------------------------------------------------
				
				styleChangerStateArrayList.add( styleChangerState);
			} // for
			
			this.reversable = reversable;
			this.repeat = repeat;
		} // public SimpleStyleProgressiveChangerTimerEvent(......)

		
		protected String get2ByteHexStringValue( int colorComponent) {
			String colorStr = Integer.toHexString( colorComponent);
			while( colorStr.length() < 2) {
				colorStr = "0" + colorStr;
			} // while
			return colorStr.substring( colorStr.length() - 2);
		}
	
		protected boolean downwardFlag = true; // For changeEventType.RoundTrip case 

		protected void setStyleToTerminalValue( StyleChangerState styleChangerState, int styleValue) {
			if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
//TODO Didn't I need to add difference unit for each of RGB spectrums here? Otherwise, I think those will create gap with styleChangerState.currentValue
				styleChangerState.styleAccessor.setStyle(
						styleChangerState.styleAccessor.getStyle(), "#" + getRGBColorHexStr( styleValue)
						);
			} // if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor())
			else { 
				styleChangerState.styleAccessor.setStyle( 
						styleChangerState.styleAccessor.getStyle(), String.valueOf( styleValue)
						);
				if ( "opacity".equalsIgnoreCase( styleChangerState.styleAccessor.getStyle())) {
					styleChangerState.styleAccessor.setStyle(
							"filter", 
							":alpha(opacity=" + String.valueOf( styleValue * 100) + ")"
							);
				}
			} // else (!styleChangerState.styleAccessor.isFlaggedAsValueForColor())
		} // protected void setStyleToTerminalValue( StyleChangerState styleChangerState, int styleValue)
		
		protected void setStyleToNextValue( StyleChangerState styleChangerState) {
			if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
				String colorStr = 
					get2ByteHexStringValue( styleChangerState.currentBlue);
				colorStr = 
					get2ByteHexStringValue( styleChangerState.currentGreen) + colorStr;
				colorStr = 
					get2ByteHexStringValue( styleChangerState.currentRed) + colorStr;
				
				styleChangerState.styleAccessor.setStyle( 
						styleChangerState.styleAccessor.getStyle(), "#" + colorStr);
			} // if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor())
			else { 
				styleChangerState.styleAccessor.setStyle(  
						styleChangerState.styleAccessor.getStyle(), 
						String.valueOf( styleChangerState.currentValue)
						);
				if ( "opacity".equalsIgnoreCase( styleChangerState.styleAccessor.getStyle())) {
					styleChangerState.styleAccessor.setStyle( 
							"filter", 
							":alpha(opacity=" + String.valueOf( styleChangerState.currentValue * 100) + ")"
							);
				}
			} // else ( !styleChangerState.styleAccessor.isFlaggedAsValueForColor())
		} // protected void setStyleToNextValue( StyleChangerState styleChangerState)
		
		@Override
		public void fire() {
			if ( styleChangerStateArrayList.size() < 1) {
				getTimerController().cancel( this);
				return;
			}

			if ( interval > period) return;
			
			boolean cycleEndFlag = true;
			StyleChangerState currentStyleChangerState = null;
			for( StyleChangerState styleChangerState : styleChangerStateArrayList) {
				if ( !styleChangerState.forProcessFlag) continue; // for
				
				// Set currentStyleChangerState to processing styleChangerState for later transactions of RoundTrip and Sequence types 
				currentStyleChangerState = styleChangerState;
				
				// Set next style values ----------------------------------------------------------
				styleChangerState.currentValue = 
					styleChangerState.currentValue + styleChangerState.diffUnit;
				
				styleChangerState.currentBlue = 
					styleChangerState.currentBlue + styleChangerState.diffBlueUnit;
				styleChangerState.currentGreen = 
					styleChangerState.currentGreen + styleChangerState.diffGreenUnit;
				styleChangerState.currentRed = 
					styleChangerState.currentRed + styleChangerState.diffRedUnit;
				// --------------------------------------------------------------------------------
/*
GWT.log( "initValue = " + getRGBColorHexStr( styleChangerState.styleAccessor.getInitValue()), null);
GWT.log( "endValue = " + getRGBColorHexStr( styleChangerState.styleAccessor.getEndValue()), null);
GWT.log( "currentValue = " + getRGBColorHexStr( styleChangerState.currentValue), null);
GWT.log( "diffUnit = " + getRGBColorHexStr( styleChangerState.diffUnit), null);
*/
				int styleInitValue = styleChangerState.styleAccessor.getInitValue();
				int styleEndValue = styleChangerState.styleAccessor.getEndValue();
				
				if ( styleEndValue >= styleInitValue) {
					if ( styleChangerState.diffUnit >= 0) {
						if ( styleChangerState.currentValue >= styleEndValue) {
							setStyleToTerminalValue( styleChangerState, styleEndValue);
						} // if ( styleChangerState.currentValue >= styleEndValue)
						else { 
							cycleEndFlag = false;
							
							setStyleToNextValue( styleChangerState);							
						} // else ( styleChangerState.currentValue < styleEndValue)
					} // if ( styleChangerState.diffUnit >= 0)
					else { // It's reversing
						if ( styleChangerState.currentValue <= styleInitValue) {
							setStyleToTerminalValue( styleChangerState, styleInitValue);
						} // if ( styleChangerState.currentValue <= styleInitValue)
						else { 
							cycleEndFlag = false;
							
							setStyleToNextValue( styleChangerState);							
						} // else ( styleChangerState.currentValue > styleInitValue)
					} // else ( styleChangerState.diffUnit < 0) (means reversing)
				} // if ( styleEndValue >= styleInitValue)
				else { 
					if ( styleChangerState.diffUnit <= 0) {
						if ( styleChangerState.currentValue <= styleEndValue) {
							setStyleToTerminalValue( styleChangerState, styleEndValue);
						} // if ( styleChangerState.currentValue <= styleEndValue)
						else { 
							cycleEndFlag = false;
							
							setStyleToNextValue( styleChangerState);							
						} // else ( styleChangerState.currentValue > styleEndValue)
					} // if ( styleChangerState.diffUnit <= 0)
					else { // It's reversing
						if ( styleChangerState.currentValue >= styleInitValue) {
							setStyleToTerminalValue( styleChangerState, styleInitValue);
						} // if ( styleChangerState.currentValue >= styleInitValue)
						else { 
							cycleEndFlag = false;
							
							setStyleToNextValue( styleChangerState);							
						} // else ( styleChangerState.currentValue < styleInitValue)
					} // else ( styleChangerState.diffUnit > 0) (Means reversing)
				} // else ( styleEndValue < styleInitValue)
			} // for
			
			if ( cycleEndFlag) {
				if ( reversable) {
					if ( reversingFlag) { // already reversed
						reversingFlag = !reversingFlag;
//						int styleChangerStateIndex = 0;
						switch( changeEventType) {
							case OnceAtAll:
								if ( repeat) {
									for( StyleChangerState styleChangerState : styleChangerStateArrayList) {
										styleChangerState.diffUnit = styleChangerState.diffUnit * -1;
										if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
											styleChangerState.diffBlueUnit = 
												styleChangerState.diffBlueUnit * -1;
											styleChangerState.diffGreenUnit = 
												styleChangerState.diffGreenUnit * -1;
											styleChangerState.diffRedUnit = 
												styleChangerState.diffRedUnit * -1;
										}
									} // for
								}
								else {
									getTimerController().cancel( this);
								}
								break; // case OnceAtAll:
							case RoundTrip:
							case Sequence:
								// Revert values of styleChangerState back
								currentStyleChangerState.forProcessFlag = false;
								currentStyleChangerState.diffUnit = currentStyleChangerState.diffUnit * -1;
								if ( currentStyleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
									currentStyleChangerState.diffBlueUnit = 
										currentStyleChangerState.diffBlueUnit * -1;
									currentStyleChangerState.diffGreenUnit = 
										currentStyleChangerState.diffGreenUnit * -1;
									currentStyleChangerState.diffRedUnit = 
										currentStyleChangerState.diffRedUnit * -1;
								}
								
								int styleChangerStateIndex = 
									styleChangerStateArrayList.indexOf( currentStyleChangerState);
								switch( changeEventType) {
									case RoundTrip:
										// Progress changingStyleStateIndex according to downwardFlag -----
										if ( downwardFlag) {
											styleChangerStateIndex++;
											if ( styleChangerStateIndex >= styleChangerStateArrayList.size()) {
												downwardFlag = !downwardFlag;
												styleChangerStateIndex = styleChangerStateArrayList.size() - 2;
											}
										}
										else { // !downwardFlag
											styleChangerStateIndex--;
											if ( styleChangerStateIndex < 0) { // Already finished round trip
												downwardFlag = !downwardFlag;
												styleChangerStateIndex = 1;
												if ( !repeat) {
													getTimerController().cancel( this);
												}
											}
										}
										// ----------------------------------------------------------------
										break; // case RoundTrip:
									case Sequence:
										// Progress styleChangerStateIndex according to downwardFlag -----
										styleChangerStateIndex++;
										if ( styleChangerStateIndex >= styleChangerStateArrayList.size()) {
											styleChangerStateIndex = 0;
											if ( !repeat) {
												getTimerController().cancel( this);
											}
										}
										// ----------------------------------------------------------------
										break; // case Sequence:
								} // switch
								
								// Mark next ChangingStyleState object to process
								styleChangerStateArrayList.get( styleChangerStateIndex).forProcessFlag = true;
								
								break; // case RoundTrip & Sequence
						} // switch
					}
					else { // !reversingFlag (Has not reversed yet) 
						reversingFlag = !reversingFlag;
						for( StyleChangerState styleChangerState : styleChangerStateArrayList) {
							if ( !styleChangerState.forProcessFlag) continue;
							styleChangerState.diffUnit = styleChangerState.diffUnit * -1;
							if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
								styleChangerState.diffBlueUnit = styleChangerState.diffBlueUnit * -1;
								styleChangerState.diffGreenUnit = styleChangerState.diffGreenUnit * -1;
								styleChangerState.diffRedUnit = styleChangerState.diffRedUnit * -1;
							}
						} // for
					}
				}
				else { // !reversable
					switch( changeEventType) {
						case OnceAtAll:
							if ( repeat) {
								for( StyleChangerState styleChangerState : styleChangerStateArrayList) {
									int styleInitValue = styleChangerState.styleAccessor.getInitValue();
									styleChangerState.currentValue = styleInitValue;
									if ( styleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
										styleChangerState.currentBlue = styleChangerState.initBlue;
										styleChangerState.currentGreen = styleChangerState.initGreen;
										styleChangerState.currentRed = styleChangerState.initRed;
										styleChangerState.styleAccessor.setStyle( 
												styleChangerState.styleAccessor.getStyle(), 
												"#" + getRGBColorHexStr( styleInitValue)
												);
									}
									else {
										styleChangerState.styleAccessor.setStyle( 
												styleChangerState.styleAccessor.getStyle(), 
												String.valueOf( styleInitValue)
												);
										if ( 
												"opacity".equalsIgnoreCase( 
														styleChangerState.styleAccessor.getStyle()
														)
												) 
										{
											styleChangerState.styleAccessor.setStyle( 
													"filter", 
													":alpha(opacity=" + String.valueOf( styleInitValue * 100) + ")"
													);
										}
									}
								} // for
							}
							else { // !repeat
								getTimerController().cancel( this);
							}
								
							break; // case OnceAtAll:
						case RoundTrip:
						case Sequence:
							// Reinitialize values of currentStyleChangerState
							currentStyleChangerState.forProcessFlag = false;
							int currentStyleInitValue = 
								currentStyleChangerState.styleAccessor.getInitValue();
							currentStyleChangerState.currentValue = currentStyleInitValue; 
							if ( currentStyleChangerState.styleAccessor.isFlaggedAsValueForColor()) {
								currentStyleChangerState.styleAccessor.setStyle( 
										currentStyleChangerState.styleAccessor.getStyle(), 
										"#" + getRGBColorHexStr( currentStyleInitValue)
										);
								currentStyleChangerState.currentBlue = currentStyleChangerState.initBlue;
								currentStyleChangerState.currentGreen = currentStyleChangerState.initGreen;
								currentStyleChangerState.currentRed = currentStyleChangerState.initRed;
							}
							else {
								currentStyleChangerState.styleAccessor.setStyle( 
										currentStyleChangerState.styleAccessor.getStyle(), 
										String.valueOf( currentStyleInitValue)
										);
								if ( 
										"opacity".equalsIgnoreCase( 
												currentStyleChangerState.styleAccessor.getStyle()
												)
										) 
								{
									currentStyleChangerState.styleAccessor.setStyle( 
											"filter", 
											":alpha(opacity=" + String.valueOf( currentStyleInitValue * 100) + ")"
											);
								}
							}
							
							int styleChangerStateIndex = 
								styleChangerStateArrayList.indexOf( currentStyleChangerState);
							switch( changeEventType) {
								case RoundTrip:
									// Progress styleChangerStateIndex according to downwardFlag -----
									if ( downwardFlag) {
										styleChangerStateIndex++;
										if ( styleChangerStateIndex >= styleChangerStateArrayList.size()) {
											downwardFlag = !downwardFlag;
											styleChangerStateIndex = styleChangerStateArrayList.size() - 2;
										}
									}
									else { // !downwardFlag
										styleChangerStateIndex--;
										if ( styleChangerStateIndex < 0) { // Already finished round trip
											downwardFlag = !downwardFlag;
											styleChangerStateIndex = 1;
											if ( !repeat) {
												getTimerController().cancel( this);
											}
										}
									}
									// ----------------------------------------------------------------
									break; // case RoundTrip:
								case Sequence:
									// Progress styleChangerStateIndex according to downwardFlag -----
									styleChangerStateIndex++;
									if ( styleChangerStateIndex >= styleChangerStateArrayList.size()) {
										styleChangerStateIndex = 0;
										if ( !repeat) {
											getTimerController().cancel( this);
										}
									}
									// ----------------------------------------------------------------
									break; // case Sequence:
							} // switch
							
							// Mark next ChangingStyleState object to process
							styleChangerStateArrayList.get( styleChangerStateIndex).forProcessFlag = true;
							
							break; // case RoundTrip & Sequence:
					} // switch
				} 
			} // if ( cycleEndFlag)
			// else // !cycleEndFlag { // Do nothing here}
			
		} // public void fire()
	} // protected class BlinkHighlightTimerEvent 

	
	// For list of objects subclassed from StyleChangerBaseUnit -----------------------------------
	protected LinkedHashSet<StyleAccessor> styleAccessorLinkedHashSet = new LinkedHashSet<StyleAccessor>();
	
	/**
	 * Add styleChangerBaseUnit to changingStyleLinkedHashSet
	 * @param styleChangerBaseUnit
	 * 
	 */
	protected boolean addChangingStyleEntry( StyleChangerBaseUnit styleChangerBaseUnit) {
		// Validation before adding
		if ( styleChangerBaseUnit == null) return false;

		if ( styleChangerBaseUnit.style == null) return false;
		styleChangerBaseUnit.style = styleChangerBaseUnit.style.trim();
		if ( "".equals( styleChangerBaseUnit.style)) return false;
		
		Element element;
		if ( styleChangerBaseUnit instanceof CssRuleStyleChangerUnit) {
			CssRuleStyleChangerUnit cssRuleStyleChangerUnit = 
				(CssRuleStyleChangerUnit)styleChangerBaseUnit;
			JsCssRule jsCssRule = cssRuleStyleChangerUnit.getCssRule();
			if ( jsCssRule == null) return false;
			if ( jsCssRule.getSelectorText() == null) return false;
			return styleAccessorLinkedHashSet.add( cssRuleStyleChangerUnit);
		}
		else 
		{ // styleChangerBaseUnit is either ElementStyleChangerUnit object or DomIdStyleChangerUnit
			ElementStyleChangerUnit elementStyleChangerUnit = (ElementStyleChangerUnit)styleChangerBaseUnit;
			element = elementStyleChangerUnit.getElement();
			if ( element == null) return false;
			if ( element.getNodeType() != 1) return false;
			if ( styleChangerBaseUnit instanceof DomIdStyleChangerUnit) {
				DomIdStyleChangerUnit domIdStyleChangerUnit = (DomIdStyleChangerUnit)styleChangerBaseUnit;
				return styleAccessorLinkedHashSet.add( domIdStyleChangerUnit);
			}
			else {
				return styleAccessorLinkedHashSet.add( elementStyleChangerUnit);			
			}
		}
	} // public boolean addChangingStyleEntry( StyleChangerBaseUnit styleChangerBaseUnit)
	
	/**
	 * Add style change for scheduling. 
	 * @param id: id of DOM element for this style change
	 * @param style: camel-case name of style what is being subject of scheduling
	 * @param initValue: beginning value of style. This will be set when processStyleChange method is called
	 * @param endValue: ending value of style
	 * @param isValueForColor: Provide true when initValue and endValue are numeric values for style related to coloring 
	 * @return true when adding style change for schedule is succeeded.
	 */
	public DomIdStyleChangerUnit addChangingStyleEntry( 
			String id, String style, int initValue, int endValue, boolean isValueForColor) 
	{
		DomIdStyleChangerUnit domIdStyleChangerUnit = 
			new DomIdStyleChangerUnit( id, style, initValue, endValue, isValueForColor);
		if ( !addChangingStyleEntry( domIdStyleChangerUnit)) return null;
		return domIdStyleChangerUnit;
	}
	public ElementStyleChangerUnit addChangingStyleEntry( 
			Element element, String style, int initValue, int endValue, boolean isValueForColor) 
	{
		ElementStyleChangerUnit elementStyleChangerUnit = 
			new ElementStyleChangerUnit( element, style, initValue, endValue, isValueForColor);
		if ( !addChangingStyleEntry( elementStyleChangerUnit)) return null;
		return elementStyleChangerUnit;
	}
	public CssRuleStyleChangerUnit addChangingStyleEntry(
			JsCssRule jsCssRule, String style, int initValue, int endValue, boolean isValueForColor) {
		CssRuleStyleChangerUnit cssRuleStyleChangerUnit = 
			new CssRuleStyleChangerUnit( jsCssRule, style, initValue, endValue, isValueForColor);
		if ( !addChangingStyleEntry( cssRuleStyleChangerUnit)) return null;
		return cssRuleStyleChangerUnit;
	}
	
	public void removeChangingStyleEntry( StyleAccessor styleAccessor) {
		styleAccessorLinkedHashSet.remove( styleAccessor);
	}
	
	public LinkedHashSet<DomIdStyleAccessor> getChangingStyle( String id) {
		LinkedHashSet<DomIdStyleAccessor> linkedHashSet = new LinkedHashSet<DomIdStyleAccessor>();
		for( StyleAccessor styleAccessor : styleAccessorLinkedHashSet) {
			if ( styleAccessor instanceof DomIdStyleAccessor) {
				DomIdStyleAccessor domIdStyleAccessor = (DomIdStyleAccessor)styleAccessor;
				if ( id.equals( domIdStyleAccessor.getId())) {
					linkedHashSet.add( domIdStyleAccessor);
				}
			}
		} // for
		return linkedHashSet;
	}
	public LinkedHashSet<ElementStyleAccessor> getChangingStyle( Element element) {
		LinkedHashSet<ElementStyleAccessor> linkedHashSet = new LinkedHashSet<ElementStyleAccessor>();
		for( StyleAccessor styleAccessor : styleAccessorLinkedHashSet) {
			if ( styleAccessor instanceof ElementStyleAccessor) {
				ElementStyleAccessor elementStyleAccessor = (ElementStyleChangerUnit)styleAccessor;
				if ( element == elementStyleAccessor.getElement()) {
					linkedHashSet.add( elementStyleAccessor);
				}
			}
		} // for
		return linkedHashSet;
	}
	public LinkedHashSet<CssRuleStyleAccessor> getChangingStyle( JsCssRule jsCssRule) {
		LinkedHashSet<CssRuleStyleAccessor> linkedHashSet = new LinkedHashSet<CssRuleStyleAccessor>();
		for( StyleAccessor styleAccessor : styleAccessorLinkedHashSet) {
			if ( styleAccessor instanceof CssRuleStyleAccessor) {
				CssRuleStyleAccessor cssRuleStyleAccessor = (CssRuleStyleAccessor)styleAccessor;
				if ( jsCssRule == cssRuleStyleAccessor.getCssRule()) {
					linkedHashSet.add( cssRuleStyleAccessor);
				}
			}
		} // for
		return linkedHashSet;
	}
	// --------------------------------------------------------------------------------------------
	
	// About event type ---------------------------------------------------------------------------
	public enum ChangeEventType {
		OnceAtAll, Sequence, RoundTrip 
	}
	private ChangeEventType changeEventType = ChangeEventType.Sequence;
		public ChangeEventType getChangeEventType() {
			return changeEventType;
		}
		public void setChangeEventType(ChangeEventType changeEventType) {
			this.changeEventType = changeEventType;
		}
	// --------------------------------------------------------------------------------------------
	
	private SimpleStyleProgressiveChangerTimerEvent timerEvent;
		protected SimpleStyleProgressiveChangerTimerEvent getTimerEvent() {
			return timerEvent;
		}
		protected void setTimerEvent(SimpleStyleProgressiveChangerTimerEvent timerEvent) {
			this.timerEvent = timerEvent;
		}
	public void cancelStyleChange() {
		if ( timerEvent == null) return;
		getTimerController().cancel( timerEvent);
	}
		
	/**
	 * Start next step of change at every interval gap
	 */
	public void processStyleChange() {
		if ( styleAccessorLinkedHashSet.size() < 1) return;
		
		SimpleStyleProgressiveChangerTimerEvent timerEvent 
		= new SimpleStyleProgressiveChangerTimerEvent( 
				styleAccessorLinkedHashSet, changeEventType, interval, period, reversable, repeat);
			setTimerEvent( timerEvent);
			getTimerController().scheduleRepeating( timerEvent, interval);
	}
}
